﻿using RentalSystem.Data.EF.Core;
using Web.Context;
using RentalSystem.Data.EF;
using System;
using Web.Models;
using System.Web;
using System.IO;
using System.Collections.Generic;

namespace Web.Helper
{
    public static class EntityManager
    {
        public static readonly string Stats_MoviesTotalCount = "MoviesTotalCount";
        public static readonly string Stats_UsersTotalCount = "UsersTotalCount";
        public static readonly string Stats_MoviesTotalTime = "MoviesTotalTime";
        public static readonly string Stats_RentalsTotalCount = "RentalsTotalCount";

        static IDataContext dataContext;

        static EntityManager()
        {
            dataContext = CurrentContext.Context.DataContext;
        }

        public static string ValidateNewUser(Users user)
        {
            if (user == null)
                throw new ArgumentNullException();

            if (dataContext.UsersDao.GetByEmail(user.UserEmail) != null)
                return "Użytkownik o podanym adresie email jest zarejestrowany.";

            //TODO: check is user, customer or person exist in database and any others data
            //throw new NotImplementedException();

            return null;
        }

        public static Users FillDefaultUserData(Users user, string password, UserRolesTypes userType)
        {
            if (user == null)
                throw new ArgumentNullException();

            Users defaultUser = user;

            defaultUser.UserGuid = Helper.GenerateGuid();
            defaultUser.UserRoleID = (int)userType;
            defaultUser.UserDateCreated = DateTime.Now;
            defaultUser.UserPassword = Crypto.ComputeMD5(password);
            defaultUser.UserAccountIsActive = true;
            defaultUser.UserIsNewsletterActive = false;
            defaultUser.AgencyId = 1;

            defaultUser.Customers.Persons.Country = "Polska";
            defaultUser.Customers.Persons.PersonGuid = Helper.GenerateGuid();
            defaultUser.Customers.CustomerGuid = Helper.GenerateGuid();
            defaultUser.Customers.CustomerTypeID = (int)AccountType.Black;

            return defaultUser;
        }

        #region statistics

        public static void UpdateStatsNewUser()
        {
            UpdateStats(EntityManager.Stats_UsersTotalCount, 1, CurrentContext.Context.CurrentCache);
        }

        public static void UpdateStatsNewMovie(long movieLengthTimeInMinus)
        {
            UpdateStats(EntityManager.Stats_MoviesTotalCount, movieLengthTimeInMinus >= 0 ? 1 : -1, CurrentContext.Context.CurrentCache);
            UpdateStats(EntityManager.Stats_MoviesTotalTime, (float)(movieLengthTimeInMinus), CurrentContext.Context.CurrentCache);
        }

        public static void UpdateStatsNewRent()
        {
            UpdateStats(EntityManager.Stats_RentalsTotalCount, 1, CurrentContext.Context.CurrentCache);
        }

        private static void UpdateStats(string key, float valueToAdd, CurrentCache cache)
        {
            cache.StatisticsClear();
            dataContext.StatisticsDao.UpdateStats(key, valueToAdd);
        }

        #endregion

        #region files

        public static Files UploadImage(FileType imageDestiny, string uniqueName, HttpPostedFileBase imageFile, IDataContext dataContext, HttpServerUtilityBase Server)
        {
            if (imageFile == null)
                throw new ArgumentNullException("Pusty plik!");

            uniqueName = Helper.ReplaceSpaceAndSpecialChar(uniqueName);

            string ext = Path.GetExtension(imageFile.FileName);

            if (!ext.ToLower().Equals(".png") && !ext.ToLower().Equals(".jpg") && !ext.ToLower().Equals(".jpeg") && !ext.ToLower().Equals(".gif"))
                throw new ArgumentException("Nieprawidłowy format pliku");

            string destiny = string.Empty;

            switch (imageDestiny)
            {
                case FileType.LocationImage: destiny = "location";
                    break;
                case FileType.MovieImage: destiny = "movie";
                    break;
            }

            string absolutePath = Path.Combine(Server.MapPath("~/Uploads/"), destiny + "_" + uniqueName + ext);
            string absolutePathMin = Path.Combine(Server.MapPath("~/Uploads/miniatures/"), destiny + "_" + uniqueName + ext);

            string path = String.Format("{0}_{1}{2}", destiny, uniqueName, ext);
            string pathMin = String.Format("{0}_{1}{2}", destiny, uniqueName, ext);

            DeleteFileIfExist(path, dataContext, Server);
            DeleteFileIfExist(pathMin, dataContext, Server);

            Helper.ResizeImageAndSave(absolutePath, imageFile.InputStream, 900, false);
            Helper.ResizeImageAndSave(absolutePathMin, imageFile.InputStream, 250, false);

            Files file = new Files
            {
                FileType = (int)FileType.Image,
                FileUrl = path
            };

            return file;
        }

        #endregion

        #region Users and customers

        public static bool CanUserHireMovie(Users user, Movies movie, SettingsManager settings)
        {
            if (user == null)
                throw new ArgumentNullException();

            if (!user.UserAccountIsActive)
                throw new InvalidOperationException("Twoje konto jest zablokowane! Nie możesz wypożyczać filmów.");

            List<Hires> userHires = dataContext.HiresDao.GetCustomerCurrentHire(user.UserGuid);

            if (userHires.Count >= settings.HiresCountPerOneCustomer)
                throw new InvalidOperationException("Aktualnie wypożyczasz maksymalną liczbę filmów. Aby wypożyczyć kolejny zwróć jedną z posiadanych kopii.");

            foreach (Hires hire in userHires)
            {
                if (hire.Discs.Movies.MovieGuid == movie.MovieGuid)
                    throw new InvalidOperationException("Już wypożyczasz inny nośnik z wybranym filmem");
            }

            return true;
        }

        #endregion

        #region movies

        public static MovieViewModel ChangeMovieObject(Movies movie)
        {
            List<MovieKinds> selectedKinds = new List<MovieKinds>();
            List<MovieKinds> availableKinds = dataContext.MovieKindsDao.List();

            if (movie != null)
                foreach (MovieMovieKinds kind in movie.MovieMovieKinds)
                {
                    selectedKinds.Add(kind.MovieKinds);
                }

            return new MovieViewModel { Movie = movie, AvailableKinds = availableKinds, SelectedKinds = selectedKinds };
        }

        public static List<MovieViewModel> TranslateMoviesObject(List<Movies> movies)
        {
            List<MovieViewModel> result = new List<MovieViewModel>();

            foreach (Movies movie in movies)
            {
                result.Add(ChangeMovieObject(movie));
            }

            return result;
        }

        public static void UpdateMovieMovieKinds(MovieViewModel movieModel, IDataContext dataContext)
        {
            if (movieModel.Movie == null) return;

            dataContext.MoviesDao.DeleteAllMovieMovieKinds(movieModel.Guid);

            if (movieModel.PostedKinds == null || movieModel.PostedKinds.KindsIDs == null) return;

            foreach (string kind in movieModel.PostedKinds.KindsIDs)
            {
                try
                {
                    int kindID = int.Parse(kind);
                    dataContext.MoviesDao.AddMovieMovieKind(new MovieMovieKinds() { MovieGuid = movieModel.Movie.MovieGuid, MovieKindID = kindID });
                }
                catch { }
            }
        }

        public static void RateMovie(Movies movie, Votes vote, IDataContext dataContext)
        {
            if (movie == null || vote == null)
                throw new ArgumentNullException("Ocena jest niemożliwa - błąd parametrów");

            if (vote.VoteMark < 1 || vote.VoteMark > 5)
                throw new ArgumentOutOfRangeException("Podana ocena jest niepoprawna!");

            Votes existVote = dataContext.VotesDao.Get(vote.UserGuid, vote.MovieGuid);

            if (existVote != null)
                throw new InvalidOperationException("Już dokonałeś oceny tego filmu");

            dataContext.VotesDao.Add(vote);

            List<Votes> movieVotes = dataContext.VotesDao.ListMovieVotes(vote.MovieGuid);

            float newAverageMark = vote.VoteMark;

            if (movieVotes != null)
            {
                foreach (Votes item in movieVotes)
                {
                    newAverageMark += item.VoteMark;
                }

                newAverageMark = newAverageMark / (newAverageMark * 1.0f);
            }

            movie.MovieMark = newAverageMark;
            dataContext.MoviesDao.Update(movie, (uint)MovieFields.Mark);
        }

        public static List<Movies> FilterMoviesByKind(int kindId, List<Movies> movies)
        {
            if (movies == null)
                return null;

            List<Movies> result = new List<Movies>();

            foreach (Movies movie in movies)
            {
                foreach (MovieMovieKinds mmk in movie.MovieMovieKinds)
                {
                    if (mmk.MovieKindID == kindId)
                    {
                        result.Add(movie);
                        break;
                    }
                }
            }

            return result;
        }

        public static void AddCopies(DiscType type, int count, int countPerItem, Movies movie, Agencies agency)
        {
            if (count <= 0 || countPerItem <= 0 || movie == null || agency == null)
                return;

            for (int i = 0; i < count; i++)
            {
                dataContext.DiscsDao.Add(new Discs()
                {
                    DiscGuid = Helper.GenerateGuid(),
                    MovieGuid = movie.MovieGuid,
                    AgencyGuid = agency.AgencyID,
                    DiscCount = countPerItem,
                    DiscIsAvailable = true,
                    DiscIsHired = false,
                    DiscTypeID = (int)type
                });
            }
        }

        public static string RentMovie(User user, Discs disc, SettingsManager settings)
        {
            if (user == null || disc == null)
                throw new ArgumentNullException("Błąd danych");

            Users dbUser = dataContext.UsersDao.Get(user.Guid);

            if (dbUser == null || dbUser.Customers == null)
                throw new ArgumentException("Aby wypożyczać film musisz być zalogowanym użytkownikiem o uprawnieniach klienta!");

            if (!disc.DiscIsAvailable || disc.DiscIsHired)
                throw new InvalidOperationException("Wybrany nośnik jest niedostępny");

            CanUserHireMovie(dbUser, disc.Movies, settings);

            int discountInPercentage = settings.UserDiscountPerLevelInPercentage * dbUser.Customers.CustomerTypeID;

            Hires newHire = new Hires()
            {
                HireGuid = Helper.GenerateGuid(),
                UserGuid = dbUser.UserGuid,
                DiscGuid = disc.DiscGuid,
                HireSignupDate = DateTime.Now,
                HireGettingDate = null,
                HirePlanningReturningDate = DateTime.Now.AddDays(settings.HireTimeInDay),
                HireReturningDate = null,
                HirePlanningCost = (decimal)(disc.DiscTypes.DiscValue * settings.HirePricePerDay * settings.HireTimeInDay * (100 - discountInPercentage)/100),
                HireIsPaidPlanningCost = false,
                HireLateReward = 0,
                HireIsPaidLateReward = false,
                HireIsDiscReturned = false
            };

            disc.DiscIsHired = true;
            dataContext.HiresDao.Add(newHire);
            dataContext.DiscsDao.Update(disc, (uint)DiscFields.IsHired);
            UpdateStatsNewRent();
            UpdateAccountType(user, settings);

            return String.Format("Wypożyczyłeś film {0} na nośniku {1}. Pamiętaj o zwrocie do dnia {2}r. Koszt wypożyczenia to {3:0.00} zł",
                disc.Movies.MovieTitle,
                disc.DiscTypes.DiscTypeName,
                newHire.HirePlanningReturningDate.Value.ToString("dd MMMM yyyy"),
                newHire.HirePlanningCost);
        }

        public static string ReturnHiredMovie(string hired, SettingsManager settings)
        {
            if (string.IsNullOrEmpty(hired))
                throw new ArgumentNullException("Niepoprawny numer wypożyczenia");

            Hires hire = dataContext.HiresDao.Get(hired);

            if (hire == null)
                throw new ArgumentException("Nie odnaleziono wybranego wypożyczenia");

            Discs disc = hire.Discs;
            float lateReward = 0;

            if (hire.HirePlanningReturningDate.HasValue && DateTime.Now > hire.HirePlanningReturningDate)
            {
                TimeSpan difference = DateTime.Now - hire.HirePlanningReturningDate.Value;
                lateReward = difference.Days * settings.HireLateRewardPerDay;
            }

            hire.HireReturningDate = DateTime.Now;
            hire.HireIsPaidPlanningCost = true;
            hire.HireLateReward = (decimal)lateReward;
            hire.HireIsPaidLateReward = true;
            hire.HireIsDiscReturned = true;

            disc.DiscIsHired = false;
            dataContext.HiresDao.Update(hire,(uint)HireFields.All);
            dataContext.DiscsDao.Update(disc, (uint)DiscFields.IsHired);

            string msg = string.Empty;

            if (hire.HireLateReward.HasValue && hire.HireLateReward.Value > 0)
            {
                float all = (float)(hire.HirePlanningCost + hire.HireLateReward.Value);
                msg = string.Format("Nośnik nie został zwrócony w terminie. Klient powinien umieścić opłatę {0:0.00}zł - {1:0.00}zł za planowane wypożyczenie oraz {2:0.00}zł kary za przedłużenie terminu.", all, hire.HirePlanningCost, hire.HireLateReward.Value);
            }
            else
            {
                msg = string.Format("Nośnik został zwrócony w terminie. Klient powinien umieścić opłatę {0:0.00}zł", hire.HirePlanningCost);
            }

            return String.Format(msg);
        }

        public static void UpdateAccountType(User user, SettingsManager settings)
        {
            if (user == null || settings == null)
                throw new ArgumentNullException();

            Users dbUser = dataContext.UsersDao.Get(user.Guid);

            if (dbUser == null)
                throw new InvalidOperationException();

            int userHiresCount = dbUser.Hires.Count;
            int hiresCountToNextStep = settings.HiresCountToNextLevel;

            if (userHiresCount < hiresCountToNextStep)
            {
                dbUser.Customers.CustomerTypeID = (int)AccountType.Black;
            }
            else if (userHiresCount < 2 * hiresCountToNextStep)
            {
                dbUser.Customers.CustomerTypeID = (int)AccountType.Blue;
            }
            else if (userHiresCount < 3 * hiresCountToNextStep)
            {
                dbUser.Customers.CustomerTypeID = (int)AccountType.Green;
            }
            else if (userHiresCount < 4 * hiresCountToNextStep)
            {
                dbUser.Customers.CustomerTypeID = (int)AccountType.Silver;
            }
            else if (userHiresCount < 5 * hiresCountToNextStep)
            {
                dbUser.Customers.CustomerTypeID = (int)AccountType.Gold;
            }

            dataContext.CustomersDao.Update(dbUser.Customers, (uint)CustomerFields.CustomerType);
        }

        #endregion

        #region Deleting functions

        public static void DeleteFileIfExist(string path, IDataContext dataContext, HttpServerUtilityBase Server)
        {
            Files file = dataContext.FilesDao.GetByPath(path);
            if (file != null)
            {
                dataContext.MoviesDao.DeleteMovieImage(file.FileID);
                dataContext.AgenciesDao.DeleteAgencyImage(file.FileID);
                dataContext.FilesDao.DeleteByPath(path);
            }

            string absolutePath = Path.Combine(Server.MapPath("~/Uploads")) + "/" + path;

            FileInfo fileInfo = new FileInfo(absolutePath);

            if (fileInfo.Exists)
            {
                fileInfo.Delete();
            }

            absolutePath = Path.Combine(Server.MapPath("~/Uploads/miniatures")) + "/" + path;
            fileInfo = new FileInfo(absolutePath);

            if (fileInfo.Exists)
            {
                fileInfo.Delete();
            }
        }

        public static void DeleteLocation(int agencyId, IDataContext dataContext, HttpServerUtilityBase Server)
        {
            Agencies agency = dataContext.AgenciesDao.Get(agencyId);
            int defaultAgencyId = dataContext.AgenciesDao.GetIdByName("Default");

            foreach (Users user in agency.Users)
            {
                user.AgencyId = defaultAgencyId;
                dataContext.UsersDao.Update(user, (uint)UserFields.Agency);
            }

            foreach (Discs disc in agency.Discs)
            {
                disc.AgencyGuid = defaultAgencyId;
                dataContext.DiscsDao.Update(disc, (uint)DiscFields.Agency);
            }

            if (agency.Files != null)
            {
                DeleteFileIfExist(agency.Files.FileUrl, dataContext, Server);
            }

            dataContext.AgenciesDao.Delete(agencyId);
        }

        public static void DeleteMovie(Movies movie)
        {
            if (movie == null)
                return;

            foreach (Votes vote in movie.Votes)
                DeleteVote(vote);

            foreach (Comments comment in movie.Comments)
                DeleteComment(comment);

            DeleteMovieMovieKind(movie.MovieGuid);

            foreach (Discs disc in movie.Discs)
                DeleteDisc(disc);

            dataContext.MoviesDao.Delete(movie);
        }

        public static void DeleteVote(Votes vote)
        {
            if (vote == null)
                return;

            dataContext.VotesDao.Delete(vote);
        }

        public static void DeleteComment(Comments comment)
        {
            if (comment == null)
                return;

            dataContext.CommentsDao.Delete(comment);
        }

        public static void DeleteMovieMovieKind(string movieGuid)
        {
            if (movieGuid == null)
                return;

            dataContext.MoviesDao.DeleteAllMovieMovieKinds(movieGuid);
        }

        public static void DeleteDisc(Discs disc)
        {
            if (disc == null)
                return;

            foreach (Hires hire in disc.Hires)
                DeleteHire(hire);

            dataContext.DiscsDao.Delete(disc);
        }

        public static void DeleteHire(Hires hire)
        {
            if (hire == null)
                return;

            dataContext.HiresDao.Delete(hire.HireGuid);
        }

        public static void DeleteUser(string userGuid)
        {
            dataContext.UsersDao.Delete(userGuid);
        }

        #endregion
    }
}